package web.runtime;

import framework.client.WebUserAgent;
import framework.security.Account;
import framework.security.AccountLoader;
import framework.security.AuthService;
import framework.utils.JsonUtil;
import framework.utils.RequestUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import system.entity.SysFunc;
import system.entity.SysOperation;
import system.service.sys.SysConfigService;
import system.service.sys.SysFuncService;
import system.service.sys.SysOperationService;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.net.URI;
import java.util.*;

/**
 * 操作日志处理器
 */
@Aspect
@Component
@Slf4j
public class OperateLogProcess {

    private static final int MAX_PATH = 256;
    private static final int MAX_PARAM = 10 * 4096;
    private static final int MAX_BODY = 10 * 4096;

    /**
     * 排除敏感属性字段
     */
    public static final String[] EXCLUDES = {"password", "oldPassword", "newPassword"};

    @Autowired
    private SysOperationService operationService;
    @Autowired
    private AuthService authService;
    @Autowired
    private AccountLoader accountLoader;
    @Autowired
    private SysConfigService configService;
    @Autowired
    private SysFuncService funcService;


    //            GET,
    //            HEAD,
    //            POST,
    //            PUT,
    //            PATCH,
    //            DELETE,
    //            OPTIONS,
    //            TRACE;
    @Pointcut(value = "@annotation(org.springframework.web.bind.annotation.GetMapping) " +
            "|| @annotation(org.springframework.web.bind.annotation.PostMapping)" +
            "|| @annotation(org.springframework.web.bind.annotation.PutMapping)" +
            "|| @annotation(org.springframework.web.bind.annotation.DeleteMapping)")
    public void logSave() {
    }

    @AfterReturning(value = "logSave()", returning = "result")
    public void alter(JoinPoint point, Object result) {
        this.log(point, null, result);
    }

    @AfterThrowing(value = "logSave()", throwing = "err")
    public void throwing(JoinPoint point, Throwable err) {
        this.log(point, err, null);
    }

    @SneakyThrows
    private void log(JoinPoint point, Throwable ex, Object result) {
        String operate = null;

//        //method
//        MethodSignature signature = (MethodSignature) point.getSignature();
//        OperateLog operateLog = signature.getMethod().getDeclaredAnnotation(OperateLog.class);
//        if (operateLog == null) return;
//        operate = operateLog.value();

        //request
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String requestMethod = request.getMethod();
        Date now = new Date();

        //check read
        if (RequestMethod.GET.name().equals(requestMethod)) {
            if ("1".equals(this.configService.config().getOperateLogReadEnable()) == false) {
                //不记录读取操作
                return;
            }
        }

        // url check
        String requestURI = request.getRequestURI();
        if (!"/".equals(requestURI)) {
            if (requestURI.startsWith(request.getContextPath())) {
                requestURI = requestURI.substring(request.getContextPath().length());
            }
        }

        //path
        URI uri = new URI(requestURI);
        String path = uri.getPath();

        // func
//        Map<String, List<SysFunc>> map = this.funcService.loadFuncAllMap();
//        List<SysFunc> pathList = map.get(path);
//        boolean pathMatched = false;
//        if (pathList != null) {
//            for (SysFunc sysFunc : pathList) {
//                if (StringUtils.hasText(sysFunc.getFunMethod())) {
//                    if (requestMethod.equals(sysFunc.getFunMethod())) {
//                        pathMatched = true;
//                        operate = sysFunc.getName();
//                        break;
//                    }
//                } else {
//                    pathMatched = true;
//                    operate = sysFunc.getName();
//                    break;
//                }
//            }
//        }
//        if (!pathMatched) {
//            operate = "No define function";
//        }

        //获取操作功能
        SysFunc func = this.funcService.loadFuncByRequest(path, requestMethod);
        if (func == null) return;
        operate = func.getName();

        // account
        Account account = null;
        if (this.authService.isAuthenticated()) {
            account = this.accountLoader.loadUserById(this.authService.getAccountId());
        }
        Long userId = 0L;
        String username = "unknown";
        if (account != null) {
            userId = account.getId();
            username = account.getUsername();
        }

        // entity
        WebUserAgent webUserAgent = new WebUserAgent(request);
        SysOperation operation = new SysOperation();
        operation.setCreateTime(now);
        operation.setUserId(userId);
        operation.setUsername(username);
        operation.setIpaddr(RequestUtil.getClientIp());
        operation.setBrowser(webUserAgent.getBrowser());
        operation.setOs(webUserAgent.getOs());
        operation.setStatus(ex == null ? 1 : 0);
        operation.setOperateName(operate);
        operation.setRequestMethod(requestMethod);
        if (path.length() > MAX_PATH)
            operation.setRequestPath(path.substring(0, MAX_PATH));
        else
            operation.setRequestPath(path);

        //
        //if (operateLog.isSaveRequest()) {
        if ("1".equals(this.configService.config().getOperateLogRequestEnable())) {
            String param = this.getRequestParam(point, request);
            if (StringUtils.hasText(param)) {
                if (param != null && param.length() > MAX_PARAM)
                    param = param.substring(0, MAX_PARAM);
                operation.setRequestParam(param);
            }

//            GET,
//            HEAD,
//            POST,
//            PUT,
//            PATCH,
//            DELETE,
//            OPTIONS,
//            TRACE;
            if (
                    HttpMethod.POST.name().equals(requestMethod)
                            || HttpMethod.PUT.name().equals(requestMethod)
                            || HttpMethod.PATCH.name().equals(requestMethod)
            ) {
                String contentType = request.getContentType();
                if ("application/x-www-form-urlencoded".equalsIgnoreCase(contentType)) {
                    //for parameters
                } else if ("multipart/form-data".equalsIgnoreCase(contentType)) {
                    //for parameters
                } else {
                    //"application/json".equalsIgnoreCase(contentType)
                    String body = this.getRequestBody(point, request);
                    if (StringUtils.hasText(body)) {
                        if (body != null && body.length() > MAX_BODY)
                            body = body.substring(0, MAX_BODY);
                        operation.setRequestData(body);
                    }
                }
            }
        }

        //
        //if (operateLog.isSaveResponse()) {
        if ("1".equals(this.configService.config().getOperateLogResponseEnable())) {
            String s = null;
            if (result instanceof ResponseEntity) {
                Object body = ((ResponseEntity<?>) result).getBody();
                s = JsonUtil.toJsonString(body);
            } else if (result != null) {
                s = JsonUtil.toJsonString(result);
            }
            if (s != null && s.length() > MAX_BODY)
                s = s.substring(0, MAX_BODY);
            operation.setResponseData(s);
        }

        //
        if (ex != null) {
            String message = ex.getMessage();
            if (message != null) {
                if (message.length() > 1024)
                    operation.setErrorMsg(message.substring(0, 1024));
                else
                    operation.setErrorMsg(message);
            } else {
                operation.setErrorMsg("NULL");
            }
        }

        //
        this.operationService.add(operation);
    }

    /**
     * 获取请求的参数
     *
     * @param point
     * @param request
     * @return
     */
    private String getRequestParam(JoinPoint point, HttpServletRequest request) {
        Map<String, String[]> map = request.getParameterMap();
        if (map.size() > 0) {
            Map<String, String[]> map2 = new HashMap<>(map);
            for (String exclude : EXCLUDES) {
                map2.remove(exclude);
            }
            String s = JsonUtil.toJsonString(map2);
            return s;
        }
        return null;
    }

    /**
     * 获取请求体
     *
     * @param joinPoint
     * @param request
     * @return
     * @throws Exception
     */
    private String getRequestBody(JoinPoint joinPoint, HttpServletRequest request) throws Exception {
        Object[] args = joinPoint.getArgs();
        String r = null;
        if (args != null && args.length > 0) {
            if (args.length == 1) {
                if (args[0] == null) {
                    //
                } else if (!args[0].getClass().isPrimitive() && !(args[0] instanceof String)) {
                    if (!isExcludeObject(args[0])) {
                        r = JsonUtil.toJsonString(args[0]);
                    }
                } else if (!isExcludeObject(args[0])) {
                    r = JsonUtil.toJsonString(args);
                }
            }
            if (args.length > 1) {
                List<Object> params = new ArrayList<>();
                for (Object arg : args) {
                    if (isExcludeObject(arg))
                        continue;
                    params.add(arg);
                }
                r = JsonUtil.toJsonString(params);
            }
        }
        return r;
    }

    private boolean isExcludeObject(final Object o) {
        if (o == null) return true;
        if (o instanceof HttpServletRequest
                || o instanceof HttpServletResponse
                || o instanceof MultipartFile
                || o instanceof BindingResult)
            return true;
        //
        Class<?> aClass = o.getClass();
        if (aClass.isArray()) {
            return aClass.getComponentType().isAssignableFrom(MultipartFile.class);
        } else if (Collection.class.isAssignableFrom(aClass)) {
            for (Object value : (Collection) o) {
                return value instanceof MultipartFile;
            }
        }
        //
        return false;
    }
}
